﻿using System.Collections;
using System.Threading;

using Microsoft.SPOT.Hardware;

using SecretLabs.NETMF.Hardware.Netduino;

namespace Dimps.CharacterLcd
{
    class Lcd
    {
        #region Private classes
        /// <summary>
        /// Change this values according to your hardware.
        /// </summary>
        private static class LcdPins
        {
            public const Cpu.Pin DB0 = Pins.GPIO_NONE;
            public const Cpu.Pin DB1 = Pins.GPIO_NONE;
            public const Cpu.Pin DB2 = Pins.GPIO_NONE;
            public const Cpu.Pin DB3 = Pins.GPIO_NONE;
            public const Cpu.Pin DB4 = Pins.GPIO_PIN_D3;
            public const Cpu.Pin DB5 = Pins.GPIO_PIN_D4;
            public const Cpu.Pin DB6 = Pins.GPIO_PIN_D5;
            public const Cpu.Pin DB7 = Pins.GPIO_PIN_D6;

            public const Cpu.Pin E = Pins.GPIO_PIN_D2;
            public const Cpu.Pin RW = Pins.GPIO_PIN_D1;
            public const Cpu.Pin RS = Pins.GPIO_PIN_D0;
        }

        /// <summary>
        /// This class encapsulates available Lcd commands.
        /// </summary>
        private static class Commands
        {
            public const byte ClearDisplay = 0x01;
            public const byte ResetDisplayAndCursorShift = 0x02;
            public const byte InitializeFunctionSetValue = 0x33;
            public const byte Mode4BitInitializeFunctionSetValue = 0x32;

            /// <summary>
            /// Represents command "Function set".
            /// </summary>
            public class FunctionSet
            {
                private const byte functionSetDefault = 0x20;

                /// <summary>
                /// False = 5 x 8 dots font format.
                /// True = 5 x 11 dots font format.
                /// </summary>
                public bool Is5x11DotsFont { get; set; }

                /// <summary>
                /// False = 1 line display.
                /// True = 2 line display.
                /// </summary>
                public bool IsTwoLineDisplay { get; set; }

                /// <summary>
                /// False = 4 bit mode.
                /// True = 8 bit mode.
                /// </summary>
                public bool Is8BitMode { get; set; }

                public static implicit operator byte(FunctionSet item)
                {
                    byte b = functionSetDefault;

                    if (item.Is5x11DotsFont)
                    {
                        b |= bit2;
                    }

                    if (item.IsTwoLineDisplay)
                    {
                        b |= bit3;
                    }

                    if (item.Is8BitMode)
                    {
                        b |= bit4;
                    }

                    return b;
                }
            }

            /// <summary>
            /// Represents command "Display On/Off".
            /// </summary>
            public class DisplayOnOff
            {
                private const byte displayOnOffDefault = 0x08;

                /// <summary>
                /// False = display is turned off, but display data is remained in DDRAM.
                /// True = entire display is turned on.
                /// </summary>
                public bool IsDisplayOn { get; set; }

                /// <summary>
                /// False = do not show cursor.
                /// True = show cursor.
                /// </summary>
                public bool ShowCursor { get; set; }

                /// <summary>
                /// False = cursor will not blink.
                /// True = cursor is blinking.
                /// </summary>
                public bool IsCursorBlinkOn { get; set; }

                public static implicit operator byte(DisplayOnOff item)
                {
                    byte b = displayOnOffDefault;

                    if (item.IsDisplayOn)
                    {
                        b |= bit2;
                    }

                    if (item.ShowCursor)
                    {
                        b |= bit1;
                    }

                    if (item.IsCursorBlinkOn)
                    {
                        b |= bit0;
                    }

                    return b;
                }
            }

            /// <summary>
            /// Represents command "Entry Mode Set".
            /// </summary>
            public class EntryModeSet
            {
                private const byte entryModeSetDefault = 0x04;

                /// <summary>
                /// False = cursor will move to the left.
                /// True = cursor will move to the right.
                /// </summary>
                public bool MoveCursorToTheRight { get; set; }

                /// <summary>
                /// False = do not shift display.
                /// True = shift display.
                ///
                /// >>When DDRAM read (CGRAM read/write) operation or ShiftEntireDisplay = False, shift of entire display is not performed. If
                /// >>ShiftEntireDisplay = True and DDRAM write operation, shift of entire display is performed according to MoveCursorToTheRight
                /// >>value (MoveCursorToTheRight = True: shift left, MoveCursorToTheRight = False: shift right).
                /// </summary>
                public bool ShiftEntireDisplay { get; set; }

                public static implicit operator byte(EntryModeSet item)
                {
                    byte b = entryModeSetDefault;

                    if (item.MoveCursorToTheRight)
                    {
                        b |= bit1;
                    }

                    if (item.ShiftEntireDisplay)
                    {
                        b |= bit0;
                    }

                    return b;
                }
            }

            /// <summary>
            /// Represents command "Display/Cursor Shift".
            /// </summary>
            public class DisplayCursorShift
            {
                private const byte displayCursorShiftDefault = 0x10;

                /// <summary>
                /// False = shift to the left.
                /// True = shift to the right.
                /// </summary>
                public bool ShiftRight { get; set; }

                /// <summary>
                /// False = cursor will be shifted.
                /// True = display will be shifted.
                /// </summary>
                public bool ShiftDisplay { get; set; }

                public static implicit operator byte(DisplayCursorShift item)
                {
                    byte b = displayCursorShiftDefault;

                    if (item.ShiftRight)
                    {
                        b |= bit2;
                    }

                    if (item.ShiftDisplay)
                    {
                        b |= bit3;
                    }

                    return b;
                }
            }

            /// <summary>
            /// Represents command "Set DDRAM address".
            /// </summary>
            public class MoveCursorToPosition
            {
                private const byte moveCursorToPositionDefault = 0x80;

                private int lineNumber;

                /// <summary>
                /// Once there is an end of first line, there is beginning of second line.
                /// </summary>
                public int PositionInLine { get; set; }

                /// <summary>
                /// Line 0 or line 1.
                /// It just shifts position by LineNumber*0x40. It might be wrong for your display.
                /// </summary>
                public int LineNumber { get; set; }

                public static implicit operator byte(MoveCursorToPosition item)
                {
                    byte b = moveCursorToPositionDefault;
                    b |= (byte)(item.lineNumber * 0x40 + item.PositionInLine);
                    return b;
                }
            }
        }
        #endregion

        #region Constants
        public const bool High = true;
        public const bool Low = false;
        private const byte bit0 = 1;
        private const byte bit1 = 1 << 1;
        private const byte bit2 = 1 << 2;
        private const byte bit3 = 1 << 3;
        private const byte bit4 = 1 << 4;
        private const byte bit5 = 1 << 5;
        private const byte bit6 = 1 << 6;
        private const byte bit7 = 1 << 7;
        #endregion

        #region Fields
        private bool canReadBusyFlag;
        private OutputPort pinDb0;
        private OutputPort pinDb1;
        private OutputPort pinDb2;
        private OutputPort pinDb3;
        private OutputPort pinDb4;
        private OutputPort pinDb5;
        private OutputPort pinDb6;
        private IOPort pinDb7;

        private OutputPort pinE;
        private OutputPort pinRs;
        private OutputPort pinRw;

        private readonly Commands.FunctionSet functionSet;
        private Commands.DisplayOnOff displayOnOff;
        private Commands.EntryModeSet entryModeSet;
        private Commands.DisplayCursorShift displayCursorShift;
        private Commands.MoveCursorToPosition moveCursorToPosition;

        private Hashtable symbolTranslationTable;
        #endregion

        #region LCD control pins as Properties
        private bool RS
        {
            set
            {
                pinRs.Write(value);
            }
        }

        private bool E
        {
            set
            {
                pinE.Write(value);
            }
        }

        private bool RW
        {
            set
            {
                pinRw.Write(value);
            }
        }

        private byte DB
        {
            set
            {
                if (functionSet.Is8BitMode)
                {
                    pinDb0.Write((value & bit0) != 0);
                    pinDb1.Write((value & bit1) != 0);
                    pinDb2.Write((value & bit2) != 0);
                    pinDb3.Write((value & bit3) != 0);
                }
                pinDb4.Write((value & bit4) != 0);
                pinDb5.Write((value & bit5) != 0);
                pinDb6.Write((value & bit6) != 0);
                pinDb7.Write((value & bit7) != 0);
            }
        }
        #endregion

        #region Properties
        /// <summary>
        /// False = display is turned off, but display data is remained in DDRAM.
        /// True = entire display is turned on.
        /// </summary>
        public bool IsDisplayOn
        {
            get
            {
                return displayOnOff.IsDisplayOn;
            }
            set
            {
                displayOnOff.IsDisplayOn = value;
                WriteCommand(displayOnOff);
            }
        }

        /// <summary>
        /// False = do not show cursor.
        /// True = show cursor.
        /// </summary>
        public bool ShowCursor
        {
            get
            {
                return displayOnOff.ShowCursor;
            }
            set
            {
                displayOnOff.ShowCursor = value;
                WriteCommand(displayOnOff);
            }
        }

        /// <summary>
        /// False = cursor will not blink.
        /// True = cursor is blinking.
        /// </summary>
        public bool IsCursorBlinkOn
        {
            get
            {
                return displayOnOff.IsCursorBlinkOn;
            }
            set
            {
                displayOnOff.IsCursorBlinkOn = value;
                WriteCommand(displayOnOff);
            }
        }

        /// <summary>
        /// False = cursor will move to the left.
        /// True = cursor will move to the right.
        /// </summary>
        public bool MoveCursorToTheRight
        {
            get
            {
                return entryModeSet.MoveCursorToTheRight;
            }
            set
            {
                entryModeSet.MoveCursorToTheRight = value;
                WriteCommand(entryModeSet);
            }
        }

        /// <summary>
        /// False = do not shift display.
        /// True = shift display.
        ///
        /// >>When DDRAM read (CGRAM read/write) operation or ShiftEntireDisplay = False, shift of entire display is not performed. If
        /// >>ShiftEntireDisplay = True and DDRAM write operation, shift of entire display is performed according to MoveCursorToTheRight
        /// >>value (MoveCursorToTheRight = True: shift left, MoveCursorToTheRight = False: shift right).
        /// </summary>
        public bool ShiftEntireDisplay
        {
            get
            {
                return entryModeSet.ShiftEntireDisplay;
            }
            set
            {
                entryModeSet.ShiftEntireDisplay = value;
                WriteCommand(entryModeSet);
            }
        }
        #endregion

        #region Constructors
        /// <summary>
        ///	Constructor initializes Lcd display device.
        /// </summary>
        ///
        /// <param name="is8BitMode">
        /// False = 4 bit mode.
        /// True = 8 bit mode.</param>
        /// 
        /// <param name="isTwoLineDisplay">
        /// False = 1 line display.
        /// True = 2 line display.</param>
        /// 
        /// <param name="is5x11DotsFont">
        /// False = 5 x 8 dots font format.
        /// True = 5 x 11 dots font format.</param>
        public Lcd(bool is8BitMode, bool isTwoLineDisplay = false, bool is5x11DotsFont = false)
        {
            InitializeOutputPorts(is8BitMode);
            InitializeLcdVariables(is8BitMode, isTwoLineDisplay, is5x11DotsFont, out functionSet);
            InitializeSymbolTranslationTable();
            InitializeLcd();
        }

        /// <summary>
        /// Creates OutputPort variables.
        /// </summary>
        /// <param name="is8BitMode">Different sets of ports used in 8bit and in 4bit modes.</param>
        private void InitializeOutputPorts(bool is8BitMode)
        {
            if (is8BitMode)
            {
                pinDb0 = new OutputPort(LcdPins.DB0, false);
                pinDb1 = new OutputPort(LcdPins.DB1, false);
                pinDb2 = new OutputPort(LcdPins.DB2, false);
                pinDb3 = new OutputPort(LcdPins.DB3, false);
            }
            pinDb4 = new OutputPort(LcdPins.DB4, false);
            pinDb5 = new OutputPort(LcdPins.DB5, false);
            pinDb6 = new OutputPort(LcdPins.DB6, false);
            pinDb7 = new IOPort(LcdPins.DB7, false, false, Port.ResistorMode.Disabled);

            pinE = new OutputPort(LcdPins.E, false);
            pinRs = new OutputPort(LcdPins.RS, false);
            pinRw = new OutputPort(LcdPins.RW, false);
        }

        /// <summary>
        /// Created variables are used not just at LCD initialization, but by many methods later.
        /// </summary>
        /// <param name="is8BitMode">Used to initialize functionSet.</param>
        /// <param name="isTwoLineDisplay">Used to initialize functionSet.</param>
        /// <param name="is5x11DotsFont">Used to initialize functionSet.</param>
        /// <param name="functionSet">functionSet is passed as 'out' parameter from constructor to be able
        /// to initialize it here, while it is readonly field.</param>
        private void InitializeLcdVariables(bool is8BitMode, bool isTwoLineDisplay, bool is5x11DotsFont, out Commands.FunctionSet functionSet)
        {
            functionSet = new Commands.FunctionSet
                            {
                                Is5x11DotsFont = is5x11DotsFont,
                                Is8BitMode = is8BitMode,
                                IsTwoLineDisplay = isTwoLineDisplay
                            };

            displayOnOff = new Commands.DisplayOnOff
                            {
                                IsCursorBlinkOn = true,
                                IsDisplayOn = true,
                                ShowCursor = true
                            };

            entryModeSet = new Commands.EntryModeSet
                            {
                                MoveCursorToTheRight = true,
                                ShiftEntireDisplay = false
                            };

            displayCursorShift = new Commands.DisplayCursorShift
                                    {
                                        ShiftRight = true,
                                        ShiftDisplay = true
                                    };

            moveCursorToPosition = new Commands.MoveCursorToPosition
                                    {
                                        LineNumber = 0,
                                        PositionInLine = 0
                                    };
        }

        /// <summary>
        /// You must edit this character translation table according to your LCD. This one is for PC2402LRS-ANH-H with Russian letters.
        /// </summary>
        private void InitializeSymbolTranslationTable()
        {
            symbolTranslationTable = new Hashtable
                                        {
                                            {' ', (byte)32}, // Space
                                            {'!', (byte)33},
                                            {'"', (byte)34},
                                            {'#', (byte)35},
                                            {'$', (byte)36},
                                            {'%', (byte)37},
                                            {'&', (byte)38},
                                            {'\'', (byte)39}, // '
                                            {'(', (byte)40},
                                            {')', (byte)41},
                                            {'*', (byte)42},
                                            {'+', (byte)43},
                                            {',', (byte)44},
                                            {'-', (byte)45},
                                            {'.', (byte)46},
                                            {'/', (byte)47},
                                            {'0', (byte)48},
                                            {'1', (byte)49},
                                            {'2', (byte)50},
                                            {'3', (byte)51},
                                            {'4', (byte)52},
                                            {'5', (byte)53},
                                            {'6', (byte)54},
                                            {'7', (byte)55},
                                            {'8', (byte)56},
                                            {'9', (byte)57},
                                            {':', (byte)58},
                                            {';', (byte)59},
                                            {'<', (byte)60},
                                            {'=', (byte)61},
                                            {'>', (byte)62},
                                            {'?', (byte)63},
                                            {'@', (byte)64},
                                            {'A', (byte)65},
                                            {'B', (byte)66},
                                            {'C', (byte)67},
                                            {'D', (byte)68},
                                            {'E', (byte)69},
                                            {'F', (byte)70},
                                            {'G', (byte)71},
                                            {'H', (byte)72},
                                            {'I', (byte)73},
                                            {'J', (byte)74},
                                            {'K', (byte)75},
                                            {'L', (byte)76},
                                            {'M', (byte)77},
                                            {'N', (byte)78},
                                            {'O', (byte)79},
                                            {'P', (byte)80},
                                            {'Q', (byte)81},
                                            {'R', (byte)82},
                                            {'S', (byte)83},
                                            {'T', (byte)84},
                                            {'U', (byte)85},
                                            {'V', (byte)86},
                                            {'W', (byte)87},
                                            {'X', (byte)88},
                                            {'Y', (byte)89},
                                            {'Z', (byte)90},
                                            {'[', (byte)91},
                                            
                                            {']', (byte)93},
                                            {'^', (byte)94},
                                            {'_', (byte)95},
                                            
                                            {'a', (byte)97},
                                            {'b', (byte)98},
                                            {'c', (byte)99},
                                            {'d', (byte)100},
                                            {'e', (byte)101},
                                            {'f', (byte)102},
                                            {'g', (byte)103},
                                            {'h', (byte)104},
                                            {'i', (byte)105},
                                            {'j', (byte)106},
                                            {'k', (byte)107},
                                            {'l', (byte)108},
                                            {'m', (byte)109},
                                            {'n', (byte)110},
                                            {'o', (byte)111},
                                            {'p', (byte)112},
                                            {'q', (byte)113},
                                            {'r', (byte)114},
                                            {'s', (byte)115},
                                            {'t', (byte)116},
                                            {'u', (byte)117},
                                            {'v', (byte)118},
                                            {'w', (byte)119},
                                            {'x', (byte)120},
                                            {'y', (byte)121},
                                            {'z', (byte)122},
                                            
                                            {'Б', (byte)160},
                                            {'Г', (byte)161},
                                            {'Ё', (byte)162},
                                            {'Ж', (byte)163},
                                            {'З', (byte)164},
                                            {'И', (byte)165},
                                            {'Й', (byte)166},
                                            {'Л', (byte)167},
                                            {'П', (byte)168},
                                            {'У', (byte)169},
                                            {'Ф', (byte)170},
                                            {'Ч', (byte)171},
                                            {'Ш', (byte)172},
                                            {'Ъ', (byte)173},
                                            {'Ы', (byte)174},
                                            {'Э', (byte)175},
                                            {'Ю', (byte)176},
                                            {'Я', (byte)177},
                                            {'б', (byte)178},
                                            {'в', (byte)179},
                                            {'г', (byte)180},
                                            {'ё', (byte)181},
                                            {'ж', (byte)182},
                                            {'з', (byte)183},
                                            {'и', (byte)184},
                                            {'й', (byte)185},
                                            {'к', (byte)186},
                                            {'л', (byte)187},
                                            {'м', (byte)188},
                                            {'н', (byte)189},
                                            {'п', (byte)190},
                                            {'т', (byte)191},
                                            {'ч', (byte)192},
                                            {'ш', (byte)193},
                                            {'ъ', (byte)194},
                                            {'ы', (byte)195},
                                            {'ь', (byte)196},
                                            {'э', (byte)197},
                                            {'ю', (byte)198},
                                            {'я', (byte)199},
                                            
                                            {'№', (byte)204},
                                            {'¿', (byte)205},
                                            {'ƒ', (byte)206},
                                            {'£', (byte)207},
                                            
                                            {'Д', (byte)224},
                                            {'Ц', (byte)225},
                                            {'Щ', (byte)226},
                                            {'д', (byte)227},
                                            {'ф', (byte)228},
                                            {'ц', (byte)229},
                                            {'щ', (byte)230},
                                            
                                            {'~', (byte)233},
                                            
                                            {'§', (byte)253},
                                            {'¶', (byte)254},
                                            
                                            
                                            
                                            
                                            
                                            
                                            
                                            
                                            
                                            
                                            {'а', (byte)97},
                                            {'е', (byte)101},
                                            {'о', (byte)111},
                                            {'р', (byte)112},
                                            {'с', (byte)99},
                                            {'у', (byte)121},
                                            {'х', (byte)120},
                                            
                                            {'А', (byte)65},
                                            {'В', (byte)66},
                                            {'Е', (byte)69},
                                            {'К', (byte)75},
                                            {'М', (byte)77},
                                            {'Н', (byte)72},
                                            {'О', (byte)79},
                                            {'Р', (byte)80},
                                            {'С', (byte)67},
                                            {'Т', (byte)84},
                                            {'Х', (byte)88},
                                            {'Ь', (byte)98},
                                        };
        }

        /// <summary>
        /// It actually works without any delays for my system, but manual says we should wait and
        /// we cannot read Busy Flag while writing functionSet.
        /// </summary>
        private void InitializeLcd()
        {
            Thread.Sleep(50);
            WriteCommand(Commands.InitializeFunctionSetValue);
            Thread.Sleep(10);
            if (!functionSet.Is8BitMode)
            {
                WriteCommand(Commands.Mode4BitInitializeFunctionSetValue);
                Thread.Sleep(10);
            }
            WriteCommand(functionSet);
            Thread.Sleep(10);
            canReadBusyFlag = true;

            WriteCommand(displayOnOff);
            WriteCommand(Commands.ClearDisplay);
            WriteCommand(entryModeSet);
        }
        #endregion

        #region Public methods
        /// <summary>
        /// Writes a string to LCD display.
        /// </summary>
        /// <param name="s">String to write to LCD.</param>
        public void WriteString(string s)
        {
            char[] chars = s.ToCharArray();
            foreach (char c in chars)
            {
                object result = symbolTranslationTable[c];

                // Actually i should have used code that throws exception every time someone try to display unknown symbol.
                // Uncomment this code and change WriteByte command to WriteByte((byte)result); if you like exceptions.
                //
                //if (result == null)
                //{
                //    throw new ArgumentOutOfRangeException("Usage of unknown symbol: " + c);
                //}
                WriteSymbol((byte)(result ?? symbolTranslationTable['?'])); //Will write question mark instead of unknown symbol.
            }
        }

        /// <summary>
        /// Writes a symbol with specified bytecode to LCD.
        /// </summary>
        /// <param name="symbol">Bytecode of wanted symbol. Look into your LCD manual for characters table or just try every code from 0 to 255.</param>
        public void WriteSymbol(byte symbol)
        {
            WriteData(symbol, false);
        }

        /// <summary>
        /// Remove all symbols and put cursor to 0 position.
        /// </summary>
        public void ClearDisplay()
        {
            WriteCommand(Commands.ClearDisplay);
        }

        /// <summary>
        /// Resets cursor (puts it to 0 position) and resets display shift (if display was shifted).
        /// </summary>
        public void ResetDisplayAndCursorShift()
        {
            WriteCommand(Commands.ResetDisplayAndCursorShift);
        }

        /// <summary>
        /// Shift display to left or right (both lines together). It does not change character values or in-memory positions.
        /// 
        /// Use ResetDisplayAndCursorShift() method to reset shift (and cursor position).
        /// </summary>
        /// <param name="shiftRight">
        /// False = shift to the left.
        /// True = shift to the right.</param>
        public void ShiftDisplay(bool shiftRight = false)
        {
            displayCursorShift.ShiftDisplay = true;
            displayCursorShift.ShiftRight = shiftRight;
            WriteCommand(displayCursorShift);
        }

        /// <summary>
        /// Shifts cursor one position to the left or to the right.
        /// </summary>
        /// <param name="shiftRight">
        /// False = shift to the left.
        /// True = shift to the right.</param>
        public void ShiftCursor(bool shiftRight = false)
        {
            displayCursorShift.ShiftDisplay = false;
            displayCursorShift.ShiftRight = shiftRight;
            WriteCommand(displayCursorShift);
        }

        /// <summary>
        /// Moves cursor to specified position.
        /// </summary>
        /// 
        /// <param name="characterPosition">
        /// Position in line. Be careful: once there is end of first line, there is beginning of second line.
        /// Experiment with your own LCD display to determine maximum available position.</param>
        /// 
        /// <param name="lineNumber">0 for first line or 1 for second line.</param>
        public void MoveCursorTo(int characterPosition, int lineNumber = 0)
        {
            moveCursorToPosition.LineNumber = lineNumber;
            moveCursorToPosition.PositionInLine = characterPosition;
            WriteCommand(moveCursorToPosition);
        }
        #endregion

        #region Private methods
        /// <summary>
        /// Writes specified command to LCD.
        /// </summary>
        /// <param name="command">Check your display datasheet for information about possible commands.</param>
        private void WriteCommand(byte command)
        {
            WriteData(command, true);
        }

        /// <summary>
        /// Writes data to the device.
        /// </summary>
        /// <param name="data">Byte to put on pins.</param>
        /// <param name="isCommand">"Command" or "Data" mode.</param>
        private void WriteData(byte data, bool isCommand)
        {
            while (canReadBusyFlag && IsBusy())
            {
                Thread.Sleep(1);
            }
            RW = Low;
            RS = isCommand ? Low : High;
            E = High;
            DB = data;
            E = Low;

            if (!functionSet.Is8BitMode)
            {
                E = High;
                DB = (byte)(data << 4);
                E = Low;
            }
        }

        /// <summary>
        /// Reads Busy Flag.
        /// </summary>
        /// <returns>True if Lcd is busy.
        /// False if Lcd is not busy.</returns>
        private bool IsBusy()
        {
            RW = High;
            RS = Low;
            E = High;
            bool isBusy = pinDb7.Read();
            E = Low;

            if (!functionSet.Is8BitMode)
            {
                E = High;
                E = Low;
            }

            return isBusy;
        }
        #endregion
    }
}
